package org.smile.json.gateway;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.smile.beans.converter.BasicConverter;
import org.smile.beans.converter.Converter;
import org.smile.exception.ConvertException;
import org.smile.exception.JSONParseException;
import org.smile.json.JSONArray;
import org.smile.json.JSONValue;
import org.smile.json.gateway.handler.JSONServiceHandler;
import org.smile.json.gateway.handler.SmileJSONServiceHandler;
import org.smile.json.gateway.handler.SpringJSONServiceHandler;



/**
 * json gateway 
 * @author strive
 *
 */
@SuppressWarnings("serial")
public class JsonGatewayServlet extends HttpServlet {
	
	protected final static Logger logger=Logger.getLogger(JsonGatewayServlet.class);
	/**
	 * 项目是否存在spring支持
	 */
	private boolean springSupport=false;
	
	private JSONServiceHandler service;
	
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String charset = request.getCharacterEncoding();
		if(charset == null) charset = "UTF-8";
		response.setContentType("text/plain;" + charset);		
		response.setStatus(200);
		String url = request.getRequestURI();
		String outStr = "";
		if (url.endsWith(".js")) {
			outStr = generateServiceJavascript(request,response);
		}
		OutputStream out = response.getOutputStream();	
		if (outStr != null) {
			byte[] bout = outStr.getBytes(charset);
			response.setIntHeader("Content-Length", bout.length);
			out.write(bout);
			out.flush();
			out.close();
		}		
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String charset = request.getCharacterEncoding();
		if(charset == null) charset = "UTF-8";
		response.setContentType("text/plain;" + charset);		
		response.setStatus(200);
		String outStr = "";
		outStr = invokeService(request,response);
		OutputStream out = response.getOutputStream();	
		if (outStr != null) {
			byte[] bout = outStr.getBytes(charset);
			response.setIntHeader("Content-Length", bout.length);
			out.write(bout);
			out.flush();
			out.close();
		}
	}

	private String invokeService(HttpServletRequest request, HttpServletResponse response) {		
		String serviceName = request.getParameter("service");
		String methodName = request.getParameter("method");
		String strArgs = request.getParameter("arguments");	
		
		String jsonValue = null;
		
		try {
			Object service = getService(request,serviceName);
			Object result = invokeMethod(service,methodName,strArgs);
			jsonValue = JSONValue.toJSONString(result);
		} catch (Throwable e) {
			Throwable e1 = e;
			if (e instanceof InvocationTargetException) {
				e1 = ((InvocationTargetException) e).getTargetException();
			}
			JsonErrorObject jsonErrorObject = new JsonErrorObject();
			jsonErrorObject.setMessage(e1.getMessage());
			jsonErrorObject.setDetails(e1.toString());
			jsonErrorObject.setCode(e1.getClass().getName());
			try {
				jsonValue = JSONValue.toJSONString(jsonErrorObject);
			}catch (Exception me) {
				logger.error(me);
			}
			response.setStatus(500);
		}		
		return jsonValue;
	}
	
	private Object invokeMethod(Object service,String methodName,String strArgs) throws NoSuchMethodException, JSONParseException,Exception {
		Object result = null;
		JSONArray jsonArgsArray = new JSONArray(strArgs);
		Object[] jsonArgs = jsonArgsArray.toArray();
		int argsNum = jsonArgs.length;
		
		Method[] methods = service.getClass().getMethods();
		List candidateMethods = new ArrayList(); 
		for (int i=0; i<methods.length; i++) {
			Method method = methods[i];
			if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum 
					&& method.getDeclaringClass().equals(service.getClass())) {
				candidateMethods.add(method);
			}
		}
		Method targetMethod = null;
		Converter converter=new BasicConverter();
		Object[] args = null;
		for (Iterator iter=candidateMethods.iterator();iter.hasNext();){
			Method method = (Method) iter.next();
			Class[] argTypes = method.getParameterTypes();
			args = new Object[argTypes.length];
			try {			
				for (int i=0; i<argTypes.length; i++) {
					args[i] = converter.convert(argTypes[i],null,jsonArgs[i]);	
				}
			} catch (ConvertException e) {
				logger.error("参数类型转换出错",e);
				continue;
			}			
			targetMethod = method;
			break;
		}
		if (targetMethod != null) {
			result = targetMethod.invoke(service,args);
		}
		else {
			String errStr = "can not find the method [" + methodName + "] in ["+service.getClass()+"], which has " + argsNum + " arguments.";
			throw  new NoSuchMethodException(errStr);
		}
		return result;
	}
	/**
	 * 创建js文件  
	 * @param request
	 * @param response
	 * @return
	 * @throws IOException
	 */
	private String generateServiceJavascript(HttpServletRequest request, HttpServletResponse response) throws IOException {
		String url = request.getRequestURI();
		if (!url.endsWith(".js")) return "";
		String serviceName = url.substring(url.lastIndexOf("/")+1,url.lastIndexOf("."));
		String gateway = request.getServletPath();
		String path=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/";
		if (!gateway.endsWith("/")) {
			gateway += "/";
		}
		StringBuffer strbuf = new StringBuffer();
		try {
			Object service = getService(request,serviceName);
			Method[] methods = service.getClass().getMethods();
			Set methodNameSet = new TreeSet();
			for (int i=0; i<methods.length; i++) {
				Method method = methods[i];
				if (method.getDeclaringClass().equals(service.getClass())) {
					methodNameSet.add(method.getName());
				}
			}
			String[] methodNames = new String[methodNameSet.size()];
			methodNameSet.toArray(methodNames);		
			strbuf.append("//author: fivefish. 2007-02-04 \n\n");
			strbuf.append("var ").append(serviceName).append("=RemoteJsonService.extend({\n");
			strbuf.append("		jsonGateway: \"").append(path).append(gateway).append("\",\n");
			strbuf.append("		serviceName: \"").append(serviceName).append("\",\n");

			for (int i=0; i<methodNames.length; i++) {
				String methodName = methodNames[i];
				strbuf.append("		").append(methodName).append(": function(){\n");	
				strbuf.append("		           var preprocessResult = this.preprocess.apply(this,arguments);\n");
				strbuf.append("		           var data = preprocessResult.data;\n");
				strbuf.append("		           data.method = \"").append(methodName).append("\";\n");
				strbuf.append("		           this.ajaxCall(data,preprocessResult.callerResponder);\n");
				strbuf.append("		}");
				if (i != methodNames.length-1) {
					strbuf.append(",");
				}
				strbuf.append("\n");				
			}
			strbuf.append("});\n\n");			
			strbuf.append("//author: fivefish. 2007-02-04  \n");
		} catch (Exception e) {						
			strbuf = new StringBuffer();			
			strbuf.append("var str=\"generate service error: ").append(serviceName).append("\\n\";\n");
			strbuf.append("str = str + \"").append(e.getMessage()).append("\";\n");
			strbuf.append("alert(str);\n");
		}
		return strbuf.toString();
	}
	private Object getService(HttpServletRequest request,String serviceName) throws Exception {
		return service.getBean(serviceName, request.getSession().getServletContext());
	}

	@Override
	public void init() throws ServletException {
		super.init();
		String spring=this.getInitParameter("springSupport");
		if(spring!=null){
			this.springSupport=Boolean.parseBoolean(spring);
		}
		if(this.springSupport){
			service=new SpringJSONServiceHandler();
		}else{
			service=new SmileJSONServiceHandler();
		}
	}
}
